package kubeapi

import (
	"context"
	"fmt"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/clientset"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/kubernetes"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	ctrl "sigs.k8s.io/controller-runtime"
	"strings"
	"sync"
)

type ResourceMeta struct {
	Name         string
	Plural       string
	Path         string
	Namespaced   bool
	GroupVersion string
}

type syncClient struct {
	client *clientset.V1Alpha1Client
	mutex  sync.Mutex
}

var (
	scheme = runtime.NewScheme()
	client = &syncClient{mutex: sync.Mutex{}}
)

var GetResourceFromKubeApi = func(ctx context.Context, client rest.Interface, resourceMeta *ResourceMeta, resourceName string, namespaceName string, getResult runtime.Object) error {
	request := client.Get().
		Resource(resourceMeta.Plural).
		Name(resourceName).
		Prefix(resourceMeta.Path, resourceMeta.GroupVersion)

	if resourceMeta.Namespaced {
		request.Namespace(namespaceName)
	}

	err := request.
		Do(ctx).
		Into(getResult)
	return err
}

var GetKBLTClient = func() (*clientset.V1Alpha1Client, error) {
	client.mutex.Lock()
	defer client.mutex.Unlock()

	if client.client == nil {
		var err error
		utilruntime.Must(clientgoscheme.AddToScheme(scheme))
		utilruntime.Must(v1alpha1.AddToScheme(scheme))
		//+kubebuilder:scaffold:scheme

		configConfs := ctrl.GetConfigOrDie()
		err = v1alpha1.AddToScheme(clientgoscheme.Scheme)
		if err != nil {
			logs.Errorf("v1alpha1.AddToScheme error: %v", err)
			return nil, err
		}
		client.client, err = clientset.NewForConfig(configConfs)
		if err != nil {
			return nil, err
		}
	}

	return client.client, nil
}

var GetClient = func() *kubernetes.Clientset {
	config := ctrl.GetConfigOrDie()
	config.QPS = 100
	config.Burst = 50
	clt, err := kubernetes.NewForConfig(config)
	if err != nil {
		logs.Error("GetCoreClient: failed to init kubernetes client: " + err.Error())
		panic(err.Error())
	}

	return clt
}

var _resourceMetaCache = map[string]ResourceMeta{}
var _resourcesCache = []*v1.APIResourceList{}

var InvalidateCacheAndTryGetResourceMeta = func(client *kubernetes.Clientset, kind string, version string) *ResourceMeta {
	_resourcesCache = []*v1.APIResourceList{}
	return GetResourceMeta(client, kind, version)
}

var GetResourceMeta = func(client *kubernetes.Clientset, kind string, version string) *ResourceMeta {
	meta := CheckResourceMeta(client, kind, version)
	if meta != nil {
		return meta
	}
	_resourcesCache = []*v1.APIResourceList{}
	meta = CheckResourceMeta(client, kind, version)
	if meta != nil {
		return meta
	}
	return nil
}

var CheckResourceMeta = func(client *kubernetes.Clientset, kind string, version string) *ResourceMeta {
	name := fmt.Sprintf("%s-%s", kind, version)
	result, ok := _resourceMetaCache[name]
	if ok {
		return &result
	}

	if len(_resourcesCache) == 0 {
		var err error
		_, _resourcesCache, err = client.ServerGroupsAndResources()
		if err != nil {
			logs.Error(fmt.Sprintf("FactoryController: GetResourceMeta failed: %s", err.Error()))
			return nil
		}
	}

	path := ""
	for _, resource := range _resourcesCache {
		if strings.ToLower(resource.GroupVersion) == strings.ToLower(version) {
			for _, apiResource := range resource.APIResources {
				if strings.ToLower(apiResource.Kind) == strings.ToLower(kind) {
					if strings.ToLower(resource.GroupVersion) == "v1" {
						path = "api"
					} else {
						path = "apis"
					}

					meta := ResourceMeta{
						Name:         apiResource.Kind,
						Plural:       apiResource.Name,
						Path:         path,
						Namespaced:   apiResource.Namespaced,
						GroupVersion: resource.GroupVersion,
					}
					_resourceMetaCache[name] = meta

					return &meta
				}
			}
		}
	}

	return nil
}

var GetRawResourceFromKubeApi = func(ctx context.Context, client rest.Interface, resourceMeta *ResourceMeta, resourceName string, namespaceName string) ([]byte, error) {
	request := client.Get().
		Resource(resourceMeta.Plural).
		Name(resourceName).
		Prefix(resourceMeta.Path, resourceMeta.GroupVersion)

	if resourceMeta.Namespaced {
		request.Namespace(namespaceName)
	}

	return request.DoRaw(ctx)
}

var CreateResourceInKubeApi = func(ctx context.Context, client rest.Interface, resourceMeta *ResourceMeta, jsBody []byte, namespaceName string) ([]byte, error, error) {
	request := client.Post().
		Resource(resourceMeta.Plural).
		Prefix(resourceMeta.Path, resourceMeta.GroupVersion).
		Body(jsBody)

	if resourceMeta.Namespaced {
		request.Namespace(namespaceName)
	}

	result := request.Do(ctx)
	e := result.Error()
	raw, err := result.Raw()
	return raw, err, e
}
